package ee.ut.aa.neuraltic.genetic;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import org.apache.log4j.Logger;

import ee.ut.aa.neuraltic.logic.PopulationStats;
import ee.ut.aa.neuraltic.neural.Layer;
import ee.ut.aa.neuraltic.neural.Network;
import ee.ut.aa.neuraltic.neural.Neuron;
import ee.ut.aa.neuraltic.neural.Synaps;
import ee.ut.aa.neuraltic.neural.TicNetwork;

public class Knowledge {

	private static Logger log = Logger.getLogger( Knowledge.class );

	Random random = new Random();

	public Network chooseMate( List<Network> population ) {

		return population.get( random.nextInt( Brain.POPULATION ) );
	}

	public Network mate( Network father, Network mother ) {

		Network child = new TicNetwork();

		int nrOfSynaps = 0;

		for( int i = 0; i < father.getLayers().size() - 1; i++ ) {

			int thisLayer = father.getLayers().get( i ).getNeurons().size();
			int nextLayer = father.getLayers().get( i + 1 ).getNeurons().size();

			nrOfSynaps = nrOfSynaps + ( thisLayer * nextLayer );
		}

		double[] fatherGenes = new double[nrOfSynaps];
		double[] motherGenes = new double[nrOfSynaps];
		double[] childGenes = new double[nrOfSynaps];

		int indx = 0;
		for( Layer layer : father.getLayers() ) {
			for( Neuron neuron : layer.getNeurons() ) {
				for( Synaps synaps : neuron.getSynapses() ) {
					fatherGenes[indx++] = synaps.getWeight();
				}
			}
		}

		indx = 0;
		for( Layer layer : mother.getLayers() ) {
			for( Neuron neuron : layer.getNeurons() ) {
				for( Synaps synaps : neuron.getSynapses() ) {
					motherGenes[indx++] = synaps.getWeight();
				}
			}
		}

		for( int i = 0; i < nrOfSynaps; i++ ) {

			if( random.nextDouble() < 0.5 )
				childGenes[i] = fatherGenes[i];
			else
				childGenes[i] = motherGenes[i];
		}
		
		indx = 0;
		for( Layer layer : child.getLayers() ) {
			for( Neuron neuron : layer.getNeurons() ) {
				for( Synaps synaps : neuron.getSynapses() ) {
					synaps.setWeight( childGenes[indx++] );
				}
			}
		}

		if( log.isDebugEnabled() ) {

			log.info( "Father genes=" + Arrays.toString( fatherGenes ) );
			log.info( "Mother genes=" + Arrays.toString( motherGenes ) );
			log.info( "Child genes=" + Arrays.toString( childGenes ) );
		}

		return child;
	}

	public void mutate( Network child ) {

		for( Layer layer : child.getLayers() )
			for( Neuron neuron : layer.getNeurons() )
				for( Synaps synaps : neuron.getSynapses() )
					if( random.nextDouble() > Brain.MUTATION )
						synaps.setWeight( Math.random() * 2 - 1 );
	}

	public List<Network> naturalSelection( List<Network> newPop ) {

		log.debug( "Starting natural selection." );

		String debug = "";
		if( log.isDebugEnabled() ) {
			for( Network network : newPop ) {
				debug += network.getValue() + ";";
			}
			log.debug( "Values of networks( " + newPop.size() + "): " + debug );
		}

		Collections.sort( newPop, new NetworkComparator() );

		newPop = newPop.subList( 0, Brain.POPULATION );

		debug = "";
		if( log.isDebugEnabled() ) {
			for( Network network : newPop ) {
				debug += network.getValue() + ";";
			}
			log.debug( "Sorted newpop of networks: " + debug );
		}

		PopulationStats.logStats( newPop );
		PopulationStats.logGenerations( newPop );
		PopulationStats.logGamestats( newPop );

		for( Network network : newPop ) {
			network.setValue( 0 );
			network.increaseGeneration();
			network.winsOne = 0;
			network.drwsOne = 0;
			network.lossOne = 0;
			network.winsTwo = 0;
			network.drwsTwo = 0;
			network.lossTwo = 0;
		}

		debug = "";
		if( log.isDebugEnabled() ) {
			for( Network network : newPop ) {
				debug += network.getValue() + ";";
			}
			log.debug( "Reset values of networks: " + debug );
		}

		return newPop;
	}
}
